"""
=
M
=

M is an ultra-simple (*m* is for minimal!) Python-based templating engine. Despite its simplicity, M is very extendable and easy-to-use.

Here's a simple example of M usage:

::

    from m import Template

    data = '''\
    <h1>To-do list</h1>
    <ol>
    [% for item in todo %]
      <li>[[ item ]]</li>
    [% end %]
    </ol>\
    '''

    template = Template(data)
    print template.render({
        'todo': ['clean house', 'shop groceries', 'code python']
    })

For license information, see ``LICENSE``.

"""

import cgitb
import md5
import os
import sys

try:
    import cPickle as pickle
except ImportError:
    import pickle

from m.core import Lexer, NodeList
from m.common import tags
from m.exceptions import OpenError
from m.parser import Parser

__author__ = 'Candeo Ideas'
__license__ = 'BSD License'
__version__ = '0.1dev2'

class Template:
    """
    Template
    ========

    The basic template class, which really doesn't do much apart from calling what needs being called.
    
    """
    def __init__(self, data, use_cgitb=False, tags=tags, cache=False):
        """
        Template.__init__
        =================

        __init__(self, data, cache=False, use_cgitb=False, tags=tags) -> Template instance
        Instantiate the template class, analyze and do an initial parsing of the data. If ``use_cgitb`` is True, ``cgitb`` will be enabled.

        """
        if use_cgitb:
            cgitb.enable()

        if not isinstance(data, NodeList):
            tokens = Lexer(data).analyze(tags)
            self.nodes = Parser(tokens).parse()
            if cache:
                try:
                    pickle.dump(self.nodes, open(cache, 'wb'))
                except IOError:
                    raise OpenError('Unable to open cache file (%s). If you do not wish to cache templates, set the environment variable ``M_DISABLE_CACHE`` to a non-false value.' % cache)
        else:
            self.nodes = data

    @classmethod
    def load(cls, name, use_cache=bool(not os.environ.get('M_DISABLE_CACHE')), *args, **kwargs):
        """
        Template.load
        =============

        @classmethod load(cls, name, use_cache=True, *args, **kwargs) -> Template instance
        Load ``name`` and instantiate the class with the contents of the file. If ``use_cache`` is ``True``, the system will attempt to load a cached version of the requested template (of course ensuring that it's up-to-date).
        
        """
        path = os.environ.get('M_TEMPLATE_DIR', '.')
        suffix = os.environ.get('M_NAME_SUFFIX', '')

        filename = '%s/%s%s' % (path, name, suffix)

        if use_cache:
            cache_path = '%s/cache_%s' % (path, md5.md5(name + suffix).hexdigest())
            if os.path.exists(cache_path) and os.path.getmtime(filename) <= os.path.getmtime(cache_path):
                try:
                    nodes = pickle.load(open(cache_path, 'rb'))
                except IOError:
                    pass
                else:
                    return cls(nodes, *args, **kwargs)

        try:
            data = open(filename).read()
        except IOError:
            raise OpenError('Unable to open template file (%s).' % filename)

        if use_cache:
            return cls(data, cache=cache_path, *args, **kwargs)
        else:
            return cls(data, *args, **kwargs)

    def render(self, context, silent_errors=True):
        """
        Template.render
        ===============

        render(self, context, silent_errors=True) -> string
        Render the template in the sent context.
        
        """
        if silent_errors:
            os.environ['M_SILENT_ERRORS'] = 'true'

        return self.nodes.render(context)

def test():
    tests = (
        ((lambda T: T('[% for x in y %][[ x ]] [% end %]').render({'y': ['foo', 'bar', 'baz']})), 'foo bar baz '),
        ((lambda T: T('[% if y %][[ y ]][% end %]').render({'y': 'foo'})), 'foo'),
        ((lambda T: T('[% if y %][[ y ]][% end %]').render({'y': None})), ''),
        ((lambda T: T('[% if y %][[ y ]][% else %]bar[% end %]').render({'y': None})), 'bar')
    )

    for no, (test, result) in enumerate(tests):
        out = test(Template)
        assert out == result, "Test #%s failed -- expected %s, got %s" % (no, repr(result), repr(out))

    print "All tests were successful."

if __name__ == '__main__':
    test()
